/*
 * This file is part of Aion-Finish <Ver:3.7>
 *
 * Aion-Finish is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Aion-Finish is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Aion-Finish.  If not, see <http://www.gnu.org/licenses/>.
 */
package gameserver.services;

import gameserver.configs.main.CustomConfig;
import gameserver.configs.main.EventConfig;
import gameserver.configs.main.GroupConfig;
import gameserver.configs.main.LoggingConfig;
import gameserver.configs.main.PunishmentConfig;
import gameserver.controllers.attack.AggroInfo;
import gameserver.controllers.attack.KillList;
import gameserver.model.Race;
import gameserver.model.gameobjects.player.Player;
import gameserver.model.team.alliance.PlayerAlliance;
import gameserver.model.team.group.PlayerGroup;
import gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
import gameserver.quest.QuestEngine;
import gameserver.quest.model.QuestCookie;
import gameserver.services.abyss.AbyssPointsService;
import gameserver.services.instance.DredgionInstanceService;
import gameserver.services.item.ItemService;
import gameserver.specialfunction.PvpDropService;
import gameserver.utils.MathUtil;
import gameserver.utils.PacketSendUtility;
import gameserver.utils.audit.AuditLogger;
import gameserver.utils.stats.AbyssRankEnum;
import gameserver.utils.stats.StatFunctions;
import gameserver.world.World;
import gameserver.world.WorldType;
import gameserver.world.knownlist.Visitor;

import java.util.ArrayList;
import java.util.List;

import javolution.util.FastMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import commons.utils.i18n.CustomMessageId;
import commons.utils.i18n.LanguageHandler;

/**
 * @author Sarynth
 * @co-author Aion Germany, Dallas, Iven. Dex
 */
public class PvpService {
	
	private static Logger log = LoggerFactory.getLogger("KILL_LOG");
    public static boolean pvpZoneReward = true;
    private FastMap<Integer, KillList> pvpKillLists;

    private PvpService() {
        pvpKillLists = new FastMap<Integer, KillList>();
    }

    public static boolean getPvpZoneReward() {
        return pvpZoneReward;
    }

    public static void setPvpZoneReward(boolean reward) {
        pvpZoneReward = reward;
    }

    public static final PvpService getInstance() {
        return SingletonHolder.instance;
    }

    /**
     * @param winnerId
     * @param victimId
     * @return
     */
    private int getKillsFor(int winnerId, int victimId) {
        KillList winnerKillList = pvpKillLists.get(winnerId);

        if (winnerKillList == null) {
            return 0;
        }

        return winnerKillList.getKillsFor(victimId);
    }

    /**
     * @param winnerId
     * @param victimId
     */
    private void addKillFor(int winnerId, int victimId) {
        KillList winnerKillList = pvpKillLists.get(winnerId);

        if (winnerKillList == null) {
            winnerKillList = new KillList();
            pvpKillLists.put(winnerId, winnerKillList);
        }

        winnerKillList.addKillFor(victimId);
    }

    /**
     * @param victim
     */
    @SuppressWarnings({"incomplete-switch"})
    public void doReward(final Player victim) {
        // battlegrounds
        if (victim.getBattleGround() != null) {
            return;
        }

        // Arena Team
        if (victim.ArenaStatus > 1) {
            return;
        }

        // winner is the player that receives the kill count
        final Player winner = victim.getAggroList().getMostPlayerDamage();

        int totalDamage = victim.getAggroList().getTotalDamage();

        if ((totalDamage == 0) || (winner == null) ||
                (winner.getRace() == victim.getRace())) {
            return;
        }
        
        // Add Player Kill to record.
        // Pvp Kill Reward.
       int reduceap = PunishmentConfig.PUNISHMENT_REDUCEAP;
        	if (reduceap < 0)
        			reduceap *= -1;
        	if (reduceap > 100) {
        			reduceap = 100;
        	}
        	// Announce that player has died.
			
    		//Kill-log
    		if ((LoggingConfig.LOG_PL) || (reduceap > 0)) {
          String ip1 = winner.getClientConnection().getIP();
          String mac1 = winner.getClientConnection().getMacAddress();
          String ip2 = victim.getClientConnection().getIP();
          String mac2 = victim.getClientConnection().getMacAddress();
          if ((mac1 != null) && (mac2 != null)) {
            if ((ip1.equalsIgnoreCase(ip2)) && (mac1.equalsIgnoreCase(mac2))) {
    					AuditLogger.info(winner, "Power Leveling : " + winner.getName() + " with " + victim.getName() + ", They have the sames ip=" + ip1 + " and mac=" + mac1 + ".");
    					if (reduceap > 0) {
    						int win_ap = winner.getAbyssRank().getAp() * reduceap / 100;
    						int vic_ap = victim.getAbyssRank().getAp() * reduceap / 100;
    						AbyssPointsService.addAp(winner, -win_ap);
    						AbyssPointsService.addAp(victim, -vic_ap);
    						PacketSendUtility.sendMessage(winner, "[PL-AP] You lost " + reduceap + "% of your total ap");
    						PacketSendUtility.sendMessage(victim, "[PL-AP] You lost " + reduceap + "% of your total ap");
            }
    					return;
    				}
    				if (ip1.equalsIgnoreCase(ip2)) {
    					AuditLogger.info(winner, "Possible Power Leveling : " + winner.getName() + " with " + victim.getName() + ", They have the sames ip=" + ip1 + ".");
    					AuditLogger.info(winner, "Check if " + winner.getName() + " and " + victim.getName() + " are Brothers-Sisters-Lovers-dogs-cats...");
            }

    			}
    		}

        // Check PVP Reward is Enabled
        if (CustomConfig.PVPREWARD_ENABLE) {
            int kills = (winner.getAbyssRank().getAllKill() + 1);

            if ((kills % CustomConfig.PVPREWARD_KILLS_NEEDED1) == 0) {
                ItemService.addItem(winner,
                        CustomConfig.PVPREWARD_ITEM_REWARD1, 1);
                PacketSendUtility.sendMessage(winner, LanguageHandler.translate(CustomMessageId.PVPSERVICE_MESSAGE1, CustomConfig.PVPREWARD_ITEM_REWARD1, CustomConfig.PVPREWARD_KILLS_NEEDED1));

                switch (winner.getRace()) {
                    case ASMODIANS:

                        if (winner.getMembership() == 1) {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD4, 4);
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD6, 4);
                        } else {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD4, 2);
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD6, 2);
                        }

                        break;

                    case ELYOS:

                        if (winner.getMembership() == 1) {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD5, 4);
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD7, 4);
                        } else {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD5, 2);
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD7, 2);
                        }

                        break;
                }
            }

            if ((kills % CustomConfig.PVPREWARD_KILLS_NEEDED2) == 0) {
                ItemService.addItem(winner,
                        CustomConfig.PVPREWARD_ITEM_REWARD2, 1);
                PacketSendUtility.sendMessage(winner, LanguageHandler.translate(CustomMessageId.PVPSERVICE_MESSAGE2, CustomConfig.PVPREWARD_ITEM_REWARD2, CustomConfig.PVPREWARD_KILLS_NEEDED2));

                switch (winner.getRace()) {
                    case ASMODIANS:

                        if (winner.getMembership() == 1) {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD6, 2);
                        } else {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD6, 1);
                        }

                        break;

                    case ELYOS:

                        if (winner.getMembership() == 1) {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD7, 2);
                        } else {
                            ItemService.addItem(winner,
                                    CustomConfig.PVPREWARD_ITEM_REWARD7, 1);
                        }

                        break;
                }
            }

            if ((kills % CustomConfig.PVPREWARD_KILLS_NEEDED3) == 0) {
                ItemService.addItem(winner,
                        CustomConfig.PVPREWARD_ITEM_REWARD3, 1);
                PacketSendUtility.sendMessage(winner, LanguageHandler.translate(CustomMessageId.PVPSERVICE_MESSAGE3, CustomConfig.PVPREWARD_ITEM_REWARD3, CustomConfig.PVPREWARD_KILLS_NEEDED3));
            }
        }
        

        PacketSendUtility.sendMessage(victim,
                Integer.toString(victim.getWorldId()));
        PacketSendUtility.sendMessage(victim, Boolean.toString(pvpZoneReward));

        // Don't count kill & AP if its one race
        if (victim.getRace() == winner.getRace())
            return;

        // Don't count kill & AP if its disabled
        if ((victim.getWorldId() == 300100000) && !pvpZoneReward) {
            return;
        }

        // Add Player Kill to record.
        if (this.getKillsFor(winner.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) {
            winner.getAbyssRank().setAllKill();
        }

        // rank death notice.
        if ((victim.getWorldType() == WorldType.ABYSS) &&
                (victim.getAbyssRank().getRank().getId() >= 10)) {
            String zonename;

            if (victim.getZoneInstance() != null) {
                zonename = (victim.getZoneInstance().getZoneTemplate().getName()
                        .name());
            } else {
                zonename = "Unknown";
            }

            String raceName;

            if (victim.getCommonData().getRace() == Race.ELYOS) {
                raceName = "Elyos";
            } else {
                raceName = "Asmodians";
            }

            String rankName = "";

            // display PvP Rank
            if (victim.getAbyssRank().getRank().getId() == 10) {
                rankName = "Army 1-Star Officer";
            } else if (victim.getAbyssRank().getRank().getId() == 11) {
                rankName = "Army 2-Star Officer";
            } else if (victim.getAbyssRank().getRank().getId() == 12) {
                rankName = "Army 3-Star Officer";
            } else if (victim.getAbyssRank().getRank().getId() == 13) {
                rankName = "Army 4-Star Officer";
            } else if (victim.getAbyssRank().getRank().getId() == 14) {
                rankName = "Army 5-Star Officer";
            } else if (victim.getAbyssRank().getRank().getId() == 15) {
                rankName = "Army General";
            } else if (victim.getAbyssRank().getRank().getId() == 16) {
                rankName = "Army Great General";
            } else if (victim.getAbyssRank().getRank().getId() == 17) {
                rankName = "Army Commander";
            } else if (victim.getAbyssRank().getRank().getId() == 18) {
                rankName = "Army Governor";
            } else {
                rankName = "Unknow";
            }

            String name = victim.getName();

            for (Player p : victim.getPosition().getMapRegion().getParent().getPlayersInside()) {
                if (p == victim) {
                    continue;
                }

                PacketSendUtility.sendPacket(p,
                        SM_SYSTEM_MESSAGE.STR_ABYSS_ORDER_RANKER_DIE(raceName,
                                rankName, name, zonename));
            }
        }

        if (!CustomConfig.CHANNEL_WORLD_KILL) {
            // Announce that player has died. send for visible object
            PacketSendUtility.broadcastPacketAndReceive(victim,
                    SM_SYSTEM_MESSAGE.STR_MSG_COMBAT_FRIENDLY_DEATH_TO_B(
                            victim.getName(), winner.getName()));
        } else {
            // Announce that player has died. send for all online player's.
            World.getInstance().doOnAllPlayers(new Visitor<Player>() {
                @Override
                public void visit(Player player) {
                    PacketSendUtility.sendPacket(player,
                            SM_SYSTEM_MESSAGE.STR_MSG_COMBAT_FRIENDLY_DEATH_TO_B(
                                    victim.getName(), winner.getName()));
                }
            });
        }

        // Keep track of how much damage was dealt by players
        // so we can remove AP based on player damage...
        int playerDamage = 0;
        boolean success = false;

        // Distribute AP to groups and players that had damage.
        for (AggroInfo aggro : victim.getAggroList().getFinalDamageList(true)) {
            if (aggro.getAttacker() instanceof Player) {
                success = rewardPlayer(victim, totalDamage, aggro);
            } 
            else if (aggro.getAttacker() instanceof PlayerGroup) {
                success = rewardPlayerGroup(victim, totalDamage, aggro);
            } 
            else if (aggro.getAttacker() instanceof PlayerAlliance) {
                success = rewardPlayerAlliance(victim, totalDamage, aggro);
            }

            // Add damage last, so we don't include damage from same race. (Duels, Arena)
            if (success) {
                playerDamage += aggro.getDamage();
            }
        }

        // Apply lost AP to defeated player
        final int apLost = StatFunctions.calculatePvPApLost(victim, winner);
        final int apActuallyLost = (int) ((apLost * playerDamage) / totalDamage);

        if ((apActuallyLost > 0) && !DredgionInstanceService.isDredgion(victim.getWorldId())) {
            if (victim.isInTvt()) {
                victim.getTvt();
            } else {
                AbyssPointsService.addAp(victim, -apActuallyLost);
            }

            if (CustomConfig.PVPDROP_ENABLED) {
                PvpDropService.Pvpdrops(victim, winner);
            }
        }
        if (LoggingConfig.LOG_KILL) {
			log.info("[KILL] Player [" + winner.getName() + "] killed [" + victim.getName() + "]");
        }
    }

    /**
     * @param victim
     * @param totalDamage
     * @param aggro
     * @return true if group is not same race
     */
    private boolean rewardPlayerGroup(Player victim, int totalDamage,
                                      AggroInfo aggro) {
        if (victim.getBattleGround() != null) {
            return false;
        }

        // Arena Team
        if (victim.ArenaStatus > 1) {
            return false;
        }

        // Don't count kill & AP if its disabled
        if ((victim.getWorldId() == 300100000) && !pvpZoneReward) {
            return false;
        }

        // Reward Group
        PlayerGroup group = ((PlayerGroup) aggro.getAttacker());

        // Don't Reward Player of Same Faction.
        // TODO: NPE if leader is offline? Store race in group.
        if (group.getLeader().getObject().getRace() == victim.getRace()) {
            return false;
        }

        // Find group members in range
        List<Player> players = new ArrayList<Player>();

        // Find highest rank and level in local group
        int maxRank = AbyssRankEnum.GRADE9_SOLDIER.getId();
        int maxLevel = 0;

        //if (DredgionInstanceService2.isDredgion(victim.getWorldId())) {
        //    Player winner = victim.getAggroList().getMostPlayerDamage();
        //    DredgionInstanceService2.getInstance().doPvpReward(winner, victim);
        //
        //    return true;
        //}

        for (Player member : group.getMembers()) {
            if (MathUtil.isIn3dRange(member, victim,
                    GroupConfig.GROUP_MAX_DISTANCE)) {
                // Don't distribute AP to a dead player!
                if (!member.getLifeStats().isAlreadyDead()) {
                    QuestEngine.getInstance()
                            .onKill(new QuestCookie(victim, member, 0, 0));
                    players.add(member);

                    if (member.getLevel() > maxLevel) {
                        maxLevel = member.getLevel();
                    }

                    if (member.getAbyssRank().getRank().getId() > maxRank) {
                        maxRank = member.getAbyssRank().getRank().getId();
                    }
                }
            }
        }

        // They are all dead or out of range.
        if (players.isEmpty()) {
            return false;
        }

        int baseApReward = StatFunctions.calculatePvpApGained(victim, maxRank,
                maxLevel);
        int baseXpReward = StatFunctions.calculatePvpXpGained(victim, maxRank,
                maxLevel);
        float groupApPercentage = (float) aggro.getDamage() / totalDamage;
        int apRewardPerMember = Math.round((baseApReward * groupApPercentage) / players.size());
        int xpRewardPerMember = Math.round((baseXpReward * groupApPercentage) / players.size());

        if (apRewardPerMember > 0) {
            for (Player member : players) {
                int memberApGain = 1;
                int memberXpGain = 1;

                if ((this.getKillsFor(member.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) ||
                        member.isInTvt()) {
                    memberApGain = Math.round(apRewardPerMember * member.getRates()
                            .getApPlayerRate());
                }

                if (xpRewardPerMember > 0) {
                    memberXpGain = Math.round(xpRewardPerMember * member.getRates().getXpPlayerGainRate());
                }

                if (member.isInTvt()) {
                    member.getTvt();
                }
                /**
                 else if(players.contains(World.getInstance().findPlayer(Util.convertName(member.getCommonData().getPartner()))))
                 {
                 member.getCommonData().addAp(memberApGain);
                 }
                 */
                else {
                    member.getCommonData().addAp(memberApGain);
                }

                AbyssPointsService.addAp(member, memberApGain);
                member.getCommonData().addExp(memberXpGain);
                this.addKillFor(member.getObjectId(), victim.getObjectId());

                // Add DP Reward
                int memberCurrentDp = member.getCommonData().getDp();
                int membeDpGain = StatFunctions.calculatePvpSoloDPReward(victim,
                        member);
                member.getCommonData().setDp(membeDpGain + memberCurrentDp);
            }
        }

        return true;
    }

    /**
     * @param victim
     * @param totalDamage
     * @param aggro
     * @return true if group is not same race
     */
    private boolean rewardPlayerAlliance(Player victim, int totalDamage,
                                         AggroInfo aggro) {
        if (victim.getBattleGround() != null) {
            return false;
        }

        // Arena Team
        if (victim.ArenaStatus > 1) {
            return false;
        }

        // Don't count kill & AP if its disabled
        if ((victim.getWorldId() == 300100000) && !pvpZoneReward) {
            return false;
        }

        // Reward Alliance
        PlayerAlliance alliance = ((PlayerAlliance) aggro.getAttacker());

        // Don't Reward Player of Same Faction.
        if (alliance.getLeader().getObject().getCommonData().getRace() == victim.getRace()) {
            return false;
        }

        // Find group members in range
        List<Player> players = new ArrayList<Player>();

        // Find highest rank and level in local group
        int maxRank = AbyssRankEnum.GRADE9_SOLDIER.getId();
        int maxLevel = 0;

        for (Player member : alliance.getMembers()) {
            if (!member.isOnline()) {
                continue;
            }

            //Player member = allianceMember.getPlayer();

            if (MathUtil.isIn3dRange(member, victim,
                    GroupConfig.GROUP_MAX_DISTANCE)) {
                // Don't distribute AP to a dead player!
                if (!member.getLifeStats().isAlreadyDead()) {
                    QuestEngine.getInstance()
                            .onKill(new QuestCookie(victim, member, 0, 0));
                    players.add(member);

                    if (member.getLevel() > maxLevel) {
                        maxLevel = member.getLevel();
                    }

                    if (member.getAbyssRank().getRank().getId() > maxRank) {
                        maxRank = member.getAbyssRank().getRank().getId();
                    }
                }
            }
        }

        // They are all dead or out of range.
        if (players.isEmpty()) {
            return false;
        }

        int baseApReward = StatFunctions.calculatePvpApGained(victim, maxRank,
                maxLevel);
        int baseXpReward = StatFunctions.calculatePvpXpGained(victim, maxRank,
                maxLevel);
        float groupApPercentage = (float) aggro.getDamage() / totalDamage;
        int apRewardPerMember = Math.round((baseApReward * groupApPercentage) / players.size());
        int xpRewardPerMember = Math.round(baseXpReward * groupApPercentage / players.size());

        if (apRewardPerMember > 0) {
            for (Player member : players) {
                int memberApGain = 1;
                int memberXpGain = 1;

                if ((this.getKillsFor(member.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) ||
                        member.isInTvt()) {
                    memberApGain = Math.round(apRewardPerMember * member.getRates()
                            .getApPlayerRate());

                }

                if (xpRewardPerMember > 0) {
                    memberXpGain = Math.round(xpRewardPerMember * member.getRates().getXpPlayerGainRate());
                }

                if (member.isInTvt()) {
                    member.getTvt();
                } else {
                    member.getCommonData().addAp(memberApGain);
                }

                AbyssPointsService.addAp(member, memberApGain);
                member.getCommonData().addExp(memberXpGain);
                this.addKillFor(member.getObjectId(), victim.getObjectId());

                // Add DP Reward
                int memberCurrentDp = member.getCommonData().getDp();
                int membeDpGain = StatFunctions.calculatePvpSoloDPReward(victim,
                        member);
                member.getCommonData().setDp(membeDpGain + memberCurrentDp);
            }
        }

        return true;
    }

    /**
     * @param victim
     * @param totalDamage
     * @param aggro
     * @return true if player is not same race
     */
    private boolean rewardPlayer(Player victim, int totalDamage, AggroInfo aggro) {
        if (victim.getBattleGround() != null) {
            return false;
        }

        // Arena Team
        if (victim.ArenaStatus > 1) {
            return false;
        }

        // Don't count kill & AP if its disabled
        if ((victim.getWorldId() == 300100000) && !pvpZoneReward) {
            return false;
        }

        // Reward Player
        Player winner = ((Player) aggro.getAttacker());

        int currentDp = winner.getCommonData().getDp();

        int dpReward = StatFunctions.calculatePvpSoloDPReward(victim, winner);

        winner.getCommonData().setDp(dpReward + currentDp);

        QuestEngine.getInstance().onKill(new QuestCookie(victim, winner, 0, 0));

        // Don't Reward Player of Same Faction.
        if (winner.getRace() == victim.getRace()) {
            return false;
        }

        winner.getCommonData().setDp(dpReward + currentDp);

        int baseApReward = 1;
        int baseXpReward = 1;

        if ((this.getKillsFor(winner.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) ||
                winner.isInTvt()) {
            baseApReward = StatFunctions.calculatePvpApGained(victim,
                    winner.getAbyssRank().getRank().getId(), winner.getLevel());
            baseXpReward = StatFunctions.calculatePvpXpGained(victim,
                    winner.getAbyssRank().getRank().getId(), winner.getLevel());
        }

        int apPlayerReward = Math.round((baseApReward * winner.getRates()
                .getApPlayerRate() * aggro.getDamage()) / totalDamage);
        int xpPlayerReward = Math.round((baseXpReward * winner.getRates().getXpPlayerGainRate() * aggro.getDamage()) / totalDamage);
        if (winner.isInTvt()) {
            winner.getTvt();
        } else {
            AbyssPointsService.addAp(winner, apPlayerReward);
        }
        winner.getCommonData().addExp(xpPlayerReward);
        this.addKillFor(winner.getObjectId(), victim.getObjectId());

        return true;
    }

    public void addMight(Player player, int might) {
        if (!EventConfig.MIGHT_ENABLED)
            return;
        int realGain = (int) (might * (1 + Math.pow(player.getAbyssRank().getRank().getId(), 2) / 150));
        PacketSendUtility.sendMessage(player, "You have received " + realGain + " KILLPONTS! (total: " + (player.getCommonData().getBattleGroundPoints() + realGain) + ")");
        player.getCommonData().setBattleGroundPoints(player.getCommonData().getBattleGroundPoints() + realGain);
    }

    @SuppressWarnings("synthetic-access")
    private static class SingletonHolder {
        protected static final PvpService instance = new PvpService();
    }
}
